/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.io;

import com.jtransc.JTranscArrays;
import com.jtransc.annotation.JTranscAsync;
import com.jtransc.annotation.JTranscSync;

/**
 * A specialized {@link OutputStream} for class for writing content to an
 * (internal) byte array. As bytes are written to this stream, the byte array
 * may be expanded to hold more bytes. When the writing is considered to be
 * finished, a copy of the byte array can be requested from the class.
 *
 * @see ByteArrayInputStream
 */
public class ByteArrayOutputStream extends OutputStream {
	/**
	 * The byte array containing the bytes written.
	 */
	protected byte[] buf;

	/**
	 * The number of bytes written.
	 */
	protected int count;

	/**
	 * Constructs a new ByteArrayOutputStream with a default size of 32 bytes.
	 * If more than 32 bytes are written to this instance, the underlying byte
	 * array will expand.
	 */
	@JTranscSync
	public ByteArrayOutputStream() {
		buf = new byte[32];
	}

	/**
	 * Constructs a new {@code ByteArrayOutputStream} with a default size of
	 * {@code size} bytes. If more than {@code size} bytes are written to this
	 * instance, the underlying byte array will expand.
	 *
	 * @param size initial size for the underlying byte array, must be
	 *             non-negative.
	 * @throws IllegalArgumentException if {@code size} < 0.
	 */
	@JTranscSync
	public ByteArrayOutputStream(int size) {
		if (size >= 0) {
			buf = new byte[size];
		} else {
			throw new IllegalArgumentException("size < 0");
		}
	}

	/**
	 * Closes this stream. This releases system resources used for this stream.
	 *
	 * @throws IOException if an error occurs while attempting to close this stream.
	 */
	@Override
	@JTranscSync
	public void close() throws IOException {
		/**
		 * Although the spec claims "A closed stream cannot perform output
		 * operations and cannot be reopened.", this implementation must do
		 * nothing.
		 */
		super.close();
	}

	@JTranscSync
	private void expand(int i) {
		/* Can the buffer handle @i more bytes, if not expand it */
		if (count + i <= buf.length) {
			return;
		}

		byte[] newbuf = new byte[(count + i) * 2];
		System.arraycopy(buf, 0, newbuf, 0, count);
		buf = newbuf;
	}

	/**
	 * Resets this stream to the beginning of the underlying byte array. All
	 * subsequent writes will overwrite any bytes previously stored in this
	 * stream.
	 */
	@JTranscSync
	public void reset() {
		count = 0;
	}

	/**
	 * Returns the total number of bytes written to this stream so far.
	 *
	 * @return the number of bytes written to this stream.
	 */
	@JTranscSync
	public int size() {
		return count;
	}

	/**
	 * Returns the contents of this ByteArrayOutputStream as a byte array. Any
	 * changes made to the receiver after returning will not be reflected in the
	 * byte array returned to the caller.
	 *
	 * @return this stream's current contents as a byte array.
	 */
	@JTranscSync
	public byte[] toByteArray() {
		byte[] newArray = new byte[count];
		System.arraycopy(buf, 0, newArray, 0, count);
		return newArray;
	}

	/**
	 * Returns the contents of this ByteArrayOutputStream as a string. Any
	 * changes made to the receiver after returning will not be reflected in the
	 * string returned to the caller.
	 *
	 * @return this stream's current contents as a string.
	 */

	@Override
	@JTranscSync
	public String toString() {
		return new String(buf, 0, count);
	}

	/**
	 * Returns the contents of this ByteArrayOutputStream as a string. Each byte
	 * {@code b} in this stream is converted to a character {@code c} using the
	 * following function:
	 * {@code c == (char)(((hibyte & 0xff) << 8) | (b & 0xff))}. This method is
	 * deprecated and either {@link #toString()} or {@link #toString(String)}
	 * should be used.
	 *
	 * @param hibyte the high byte of each resulting Unicode character.
	 * @return this stream's current contents as a string with the high byte set
	 * to {@code hibyte}.
	 * @deprecated Use {@link #toString()} instead.
	 */
	@Deprecated
	@JTranscSync
	public String toString(int hibyte) {
		char[] newBuf = new char[size()];
		for (int i = 0; i < newBuf.length; i++) {
			newBuf[i] = (char) (((hibyte & 0xff) << 8) | (buf[i] & 0xff));
		}
		return new String(newBuf);
	}

	/**
	 * Returns the contents of this ByteArrayOutputStream as a string converted
	 * according to the encoding declared in {@code charsetName}.
	 *
	 * @param charsetName a string representing the encoding to use when translating
	 *                    this stream to a string.
	 * @return this stream's current contents as an encoded string.
	 * @throws UnsupportedEncodingException if the provided encoding is not supported.
	 */
	@JTranscSync
	public String toString(String charsetName) throws UnsupportedEncodingException {
		return new String(buf, 0, count, charsetName);
	}

	/**
	 * Writes {@code count} bytes from the byte array {@code buffer} starting at
	 * offset {@code index} to this stream.
	 *
	 * @param buffer the buffer to be written.
	 * @param offset the initial position in {@code buffer} to retrieve bytes.
	 * @param len    the number of bytes of {@code buffer} to write.
	 * @throws NullPointerException      if {@code buffer} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code len < 0}, or if
	 *                                   {@code offset + len} is greater than the length of
	 *                                   {@code buffer}.
	 */
	@Override
	@JTranscSync
	public void write(byte[] buffer, int offset, int len) {
		JTranscArrays.checkOffsetAndCount(buffer.length, offset, len);
		if (len == 0) {
			return;
		}
		expand(len);
		System.arraycopy(buffer, offset, buf, this.count, len);
		this.count += len;
	}

	/**
	 * Writes the specified byte {@code oneByte} to the OutputStream. Only the
	 * low order byte of {@code oneByte} is written.
	 *
	 * @param oneByte the byte to be written.
	 */
	@Override
	@JTranscSync
	public void write(int oneByte) {
		if (count == buf.length) {
			expand(1);
		}
		buf[count++] = (byte) oneByte;
	}

	/**
	 * Takes the contents of this stream and writes it to the output stream
	 * {@code out}.
	 *
	 * @param out an OutputStream on which to write the contents of this stream.
	 * @throws IOException if an error occurs while writing to {@code out}.
	 */
	@JTranscAsync
	public void writeTo(OutputStream out) throws IOException {
		out.write(buf, 0, count);
	}
}
